/*
 * Copyright (C) 2019-2020 Yaong <yaongtime@gmail.com>
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "vc4_private.h"
#include "drm-uapi/drm_fourcc.h"
#include "vk_util.h"
#include "wsi_common.h"
#include "kernel/vc4_packet.h"
#include "vc4_vk_formats.h"

#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm-uapi/drm.h>
#include "drm-uapi/vc4_drm.h"

uint32_t
vc4_layer_offset(const struct vc4_image *image, uint32_t level, uint32_t layer)
{
   const struct vc4_resource_slice *slice = &image->slices[level];

   if (image->type == VK_IMAGE_TYPE_3D)
      return image->mem_offset + slice->offset + layer * slice->size;
   else
      return image->mem_offset + slice->offset + layer * image->cube_map_stride;
}

static enum pipe_swizzle
vk_component_mapping_to_pipe_swizzle(VkComponentSwizzle comp,
                                     VkComponentSwizzle swz)
{
   if (swz == VK_COMPONENT_SWIZZLE_IDENTITY)
      swz = comp;

   switch (swz) {
   case VK_COMPONENT_SWIZZLE_ZERO:
      return PIPE_SWIZZLE_0;
   case VK_COMPONENT_SWIZZLE_ONE:
      return PIPE_SWIZZLE_1;
   case VK_COMPONENT_SWIZZLE_R:
      return PIPE_SWIZZLE_X;
   case VK_COMPONENT_SWIZZLE_G:
      return PIPE_SWIZZLE_Y;
   case VK_COMPONENT_SWIZZLE_B:
      return PIPE_SWIZZLE_Z;
   case VK_COMPONENT_SWIZZLE_A:
      return PIPE_SWIZZLE_W;
   default:
      unreachable("Unknown VkComponentSwizzle");
   };
}

enum pipe_texture_target
vc4_vk_type_to_pipe_type(VkImageType type)
{
   switch (type) {
   case VK_IMAGE_TYPE_1D: return PIPE_TEXTURE_1D;
   case VK_IMAGE_TYPE_2D: return PIPE_TEXTURE_2D;
   case VK_IMAGE_TYPE_3D: return PIPE_TEXTURE_3D;
   default:
      unreachable("Invalid image type");
   }
}

/** Return the width in pixels of a 64-byte microtile. */
static inline uint32_t
vc4_utile_width(int cpp)
{
   switch (cpp) {
   case 1:
   case 2:
            return 8;
   case 4:
            return 4;
   case 8:
            return 2;
   default:
            unreachable("unknown cpp");
   }
}

/** Return the height in pixels of a 64-byte microtile. */
static inline uint32_t
vc4_utile_height(int cpp)
{
   switch (cpp) {
   case 1:
            return 8;
   case 2:
   case 4:
   case 8:
            return 4;
   default:
            unreachable("unknown cpp");
   }
}

static bool
vc4_size_is_lt(uint32_t width, uint32_t height, int cpp)
{
   return (width <= 4 * vc4_utile_width(cpp) ||
      height <= 4 * vc4_utile_height(cpp));
}

static void
vc4_setup_slices(struct vc4_image *image)
{

   //FIXME!
   uint32_t width = image->extent.width;
   uint32_t height = image->extent.height;
   // if (prsc->format == PIPE_FORMAT_ETC1_RGB8) {
   //         width = (width + 3) >> 2;
   //         height = (height + 3) >> 2;
   // }

   uint32_t pot_width = util_next_power_of_two(width);
   uint32_t pot_height = util_next_power_of_two(height);
   uint32_t offset = 0;
   uint32_t utile_w = vc4_utile_width(image->cpp);
   uint32_t utile_h = vc4_utile_height(image->cpp);

   for (int i = image->levels - 1; i >= 0; i--) {
      struct vc4_resource_slice *slice = &image->slices[i];

      uint32_t level_width, level_height;
      if (i == 0) {
         level_width = width;
         level_height = height;
      } else {
         level_width = u_minify(pot_width, i);
         level_height = u_minify(pot_height, i);
      }

      if (!image->tiled) {
         slice->tiling = VC4_TILING_FORMAT_LINEAR;
         if (image->samples > VK_SAMPLE_COUNT_1_BIT) {
            /* MSAA (4x) surfaces are stored as raw tile buffer contents. */
            level_width = align(level_width, 32);
            level_height = align(level_height, 32);
         } else {
            level_width = align(level_width, utile_w);
         }
      } else {
         if (vc4_size_is_lt(level_width, level_height,
                              image->cpp)) {
            slice->tiling = VC4_TILING_FORMAT_LT;
            level_width = align(level_width, utile_w);
            level_height = align(level_height, utile_h);
         } else {
            slice->tiling = VC4_TILING_FORMAT_T;
            level_width = align(level_width,
                              4 * 2 * utile_w);
            level_height = align(level_height,
                              4 * 2 * utile_h);
         }
      }

      slice->offset = offset;
      slice->stride = (level_width * image->cpp *
                        MAX2(image->samples, 1));
      slice->size = level_height * slice->stride;

      offset += slice->size;

      // if (vc4_debug & VC4_DEBUG_SURFACE) {
      //         static const char tiling_chars[] = {
      //                 [VC4_TILING_FORMAT_LINEAR] = 'R',
      //                 [VC4_TILING_FORMAT_LT] = 'L',
      //                 [VC4_TILING_FORMAT_T] = 'T'
      //         };
      //         fprintf(stderr,
      //                 "rsc %s %p (format %s: vc4 %d), %dx%d: "
      //                 "level %d (%c) -> %dx%d, stride %d@0x%08x\n",
      //                 caller, rsc,
      //                 util_format_short_name(prsc->format),
      //                 rsc->vc4_format,
      //                 prsc->width0, prsc->height0,
      //                 i, tiling_chars[slice->tiling],
      //                 level_width, level_height,
      //                 slice->stride, slice->offset);
      // }
   }

   /* The texture base pointer that has to point to level 0 doesn't have
      * intra-page bits, so we have to align it, and thus shift up all the
      * smaller slices.
      */
   uint32_t page_align_offset = (align(image->slices[0].offset, 4096) -
                                 image->slices[0].offset);
   if (page_align_offset) {
      for (int i = 0; i <= image->levels - 1; i++)
         image->slices[i].offset += page_align_offset;
   }

   image->size = offset;

   /* Cube map faces appear as whole miptrees at a page-aligned offset
   * from the first face's miptree.
   */
   if (image->create_flags | VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) {
      image->cube_map_stride = align(image->slices[0].offset +
                                         image->slices[0].size,
                                     4096);
   }
}

VkResult
vc4_CreateImage(VkDevice _device,
               const VkImageCreateInfo *pCreateInfo,
               const VkAllocationCallbacks *pAllocator,
               VkImage *pImage)
{
    //FIXME!

   VC4_FROM_HANDLE(vc4_device, device, _device);
   struct vc4_image *image;

   uint64_t modifier = DRM_FORMAT_MOD_INVALID;
   if (pCreateInfo->tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) {
      const VkImageDrmFormatModifierListCreateInfoEXT *mod_info =
         vk_find_struct_const(pCreateInfo->pNext,
                              IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT);
      assert(mod_info);
      for (uint32_t i = 0; i < mod_info->drmFormatModifierCount; i++) {
         switch (mod_info->pDrmFormatModifiers[i]) {
         case DRM_FORMAT_MOD_LINEAR:
            if (modifier == DRM_FORMAT_MOD_INVALID)
               modifier = DRM_FORMAT_MOD_LINEAR;
            break;
         case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED:
            modifier = DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED;
            break;
         }
      }
   } else {
      const struct wsi_image_create_info *wsi_info =
         vk_find_struct_const(pCreateInfo->pNext, WSI_IMAGE_CREATE_INFO_MESA);
      if (wsi_info)
         modifier = DRM_FORMAT_MOD_LINEAR;
   }

   /* 1D and 1D_ARRAY textures are always raster-order */
   VkImageTiling tiling;
   if (pCreateInfo->imageType == VK_IMAGE_TYPE_1D)
      tiling = VK_IMAGE_TILING_LINEAR;
   else if (modifier == DRM_FORMAT_MOD_INVALID)
      tiling = pCreateInfo->tiling;
   else
      tiling = VK_IMAGE_TILING_LINEAR;

   if (tiling == VK_IMAGE_TILING_OPTIMAL)
      modifier = DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED;
   else if (tiling == VK_IMAGE_TILING_LINEAR)
      modifier = DRM_FORMAT_MOD_LINEAR;

   // const struct vc4__format *format = vc4_get_format(pCreateInfo->format);
   // vc4__assert(format != NULL && format->supported);

   image = vk_zalloc2(&device->vk.alloc, pAllocator, sizeof(*image), 8,
                      VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
   if (!image)
      return vk_error(device->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

   image->type = pCreateInfo->imageType;
   image->extent = pCreateInfo->extent;
   image->vk_format = pCreateInfo->format;
   // image->format = format;
   // image->aspects = vk_format_aspects(image->vk_format);
   image->levels = pCreateInfo->mipLevels;
   image->array_size = pCreateInfo->arrayLayers;
   image->samples = pCreateInfo->samples;
   image->usage = pCreateInfo->usage;
   image->create_flags = pCreateInfo->flags;

   image->drm_format_mod = modifier;
   image->tiling = tiling;
   image->tiled = tiling == VK_IMAGE_TILING_OPTIMAL;
   image->cpp = util_format_get_blocksize(vk_format_to_pipe_format(image->vk_format));

   vc4_setup_slices(image);
   *pImage = vc4_image_to_handle(image);

   return VK_SUCCESS;
}

void
vc4_DestroyImage(VkDevice _device,
                  VkImage _image,
                  const VkAllocationCallbacks* pAllocator)
{
   VC4_FROM_HANDLE(vc4_device, device, _device);
   VC4_FROM_HANDLE(vc4_image, image, _image);
   vk_free2(&device->vk.alloc, pAllocator, image);
}


VkResult
vc4_CreateImageView(VkDevice _device,
                     const VkImageViewCreateInfo *pCreateInfo,
                     const VkAllocationCallbacks *pAllocator,
                     VkImageView *pView)
{
   VC4_FROM_HANDLE(vc4_device, device, _device);
   VC4_FROM_HANDLE(vc4_image, image, pCreateInfo->image);
   struct vc4_image_view *iview;

   iview = vk_zalloc2(&device->vk.alloc, pAllocator, sizeof(*iview), 8,
                      VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
   if (iview == NULL)
      return vk_error(device->instance, VK_ERROR_OUT_OF_HOST_MEMORY);

   const VkImageSubresourceRange *range = &pCreateInfo->subresourceRange;

   assert(range->layerCount > 0);
   assert(range->baseMipLevel < image->levels);

// #ifdef DEBUG
//    switch (image->type) {
//    case VK_IMAGE_TYPE_1D:
//    case VK_IMAGE_TYPE_2D:
//       assert(range->baseArrayLayer + vc4__layer_count(image, range) - 1 <=
//              image->array_size);
//       break;
//    case VK_IMAGE_TYPE_3D:
//       assert(range->baseArrayLayer + vc4__layer_count(image, range) - 1
//              <= u_minify(image->extent.depth, range->baseMipLevel));
//       break;
//    default:
//       unreachable("bad VkImageType");
//    }
// #endif

   iview->image = image;
   iview->aspects = range->aspectMask;
   iview->type = pCreateInfo->viewType;

   iview->base_level = range->baseMipLevel;
   iview->max_level = iview->base_level + vc4_level_count(image, range) - 1;
   iview->extent = (VkExtent3D) {
      .width  = u_minify(image->extent.width , iview->base_level),
      .height = u_minify(image->extent.height, iview->base_level),
      .depth  = u_minify(image->extent.depth , iview->base_level),
   };

   iview->first_layer = range->baseArrayLayer;
   iview->last_layer = range->baseArrayLayer +
                       vc4_layer_count(image, range) - 1;
   iview->offset =
      vc4_layer_offset(image, iview->base_level, iview->first_layer);

   iview->vk_format = pCreateInfo->format;
   iview->format = vc4_get_format(pCreateInfo->format);
   assert(iview->format && iview->format->supported);
   iview->swap_rb = iview->format->swizzle[0] == PIPE_SWIZZLE_Z;

   /* FIXME: should we just move this to
    * vc4__get_internal_type_bpp_for_output_format instead?
    */
//    if (vk_format_is_depth_or_stencil(iview->vk_format)) {
//       switch (iview->vk_format) {
//       case VK_FORMAT_D16_UNORM:
//          iview->internal_type = V3D_INTERNAL_TYPE_DEPTH_16;
//          break;
//       case VK_FORMAT_D32_SFLOAT:
//          iview->internal_type = V3D_INTERNAL_TYPE_DEPTH_32F;
//          break;
//       case VK_FORMAT_X8_D24_UNORM_PACK32:
//       case VK_FORMAT_D24_UNORM_S8_UINT:
//          iview->internal_type = V3D_INTERNAL_TYPE_DEPTH_24;
//          break;
//       default:
//          assert(!"unsupported format");
//          break;
//       }
//    } else {
//       vc4_get_internal_type_bpp_for_output_format(iview->format->rt_type,
//                                                    &iview->internal_type,
//                                                    &iview->internal_bpp);
//    }

   /* FIXME: we are doing this vk to pipe swizzle mapping just to call
    * util_format_compose_swizzles. Would be good to check if it would be
    * better to reimplement the latter using vk component
    */
   uint8_t image_view_swizzle[4] = {
       vk_component_mapping_to_pipe_swizzle(VK_COMPONENT_SWIZZLE_R,
                                            pCreateInfo->components.r),
       vk_component_mapping_to_pipe_swizzle(VK_COMPONENT_SWIZZLE_G,
                                            pCreateInfo->components.g),
       vk_component_mapping_to_pipe_swizzle(VK_COMPONENT_SWIZZLE_B,
                                            pCreateInfo->components.b),
       vk_component_mapping_to_pipe_swizzle(VK_COMPONENT_SWIZZLE_A,
                                            pCreateInfo->components.a),
   };
   const uint8_t *format_swizzle = vc4_get_vkformat_swizzle(iview->vk_format);

   util_format_compose_swizzles(format_swizzle, image_view_swizzle, iview->swizzle);

//    pack_texture_shader_state(device, iview);

   *pView = vc4_image_view_to_handle(iview);

   return VK_SUCCESS;
}

void
vc4_DestroyImageView(VkDevice _device,
                     VkImageView imageView,
                     const VkAllocationCallbacks* pAllocator)
{
   VC4_FROM_HANDLE(vc4_device, device, _device);
   VC4_FROM_HANDLE(vc4_image_view, image_view, imageView);

   vk_free2(&device->vk.alloc, pAllocator, image_view);
}

VkResult
vc4_BindImageMemory(VkDevice _device,
                     VkImage _image,
                     VkDeviceMemory _memory,
                     VkDeviceSize memoryOffset)
{
   VC4_FROM_HANDLE(vc4_device_memory, mem, _memory);
   VC4_FROM_HANDLE(vc4_image, image, _image);
   VC4_FROM_HANDLE(vc4_device, device, _device);

   /* Valid usage:
    *
    *   "memoryOffset must be an integer multiple of the alignment member of
    *    the VkMemoryRequirements structure returned from a call to
    *    vkGetImageMemoryRequirements with image"
    */
   assert(memoryOffset % image->alignment == 0);
   assert(memoryOffset < mem->bo.size);

   image->mem = mem;
   image->mem_offset = memoryOffset;

   if (image->tiling == VK_IMAGE_TILING_OPTIMAL && image->drm_format_mod == DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED) {
      struct drm_vc4_set_tiling set_tiling = {
          .handle = mem->bo.handle,
          .modifier = DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED,
      };
      int ret = drmIoctl(device->fd, DRM_IOCTL_VC4_SET_TILING,
                          &set_tiling);
      if (ret != 0)
         return vk_error(device->instance, VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT);
   }

   return VK_SUCCESS;
}

void
vc4_GetImageSubresourceLayout(VkDevice device,
                               VkImage _image,
                               const VkImageSubresource *subresource,
                               VkSubresourceLayout *layout)
{
   VC4_FROM_HANDLE(vc4_image, image, _image);

   const struct vc4_resource_slice *slice =
      &image->slices[subresource->mipLevel];
   layout->offset = slice->offset;
   layout->rowPitch = slice->stride;
//    layout->depthPitch = image->cube_map_stride;
//    layout->arrayPitch = image->cube_map_stride;
   layout->size = slice->size;
}

VkResult
vc4_GetImageDrmFormatModifierPropertiesEXT(
   VkDevice device,
   VkImage _image,
   VkImageDrmFormatModifierPropertiesEXT *pProperties)
{
   VC4_FROM_HANDLE(vc4_image, image, _image);

   assert(pProperties->sType =
          VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT);

   pProperties->drmFormatModifier = image->drm_format_mod;

   return VK_SUCCESS;
}